package com.lincong.mvcframework.servlet;

import com.lincong.demo.pojo.Handler;
import com.lincong.mvcframework.annotations.CtAutowired;
import com.lincong.mvcframework.annotations.CtController;
import com.lincong.mvcframework.annotations.CtRequestMapping;
import com.lincong.mvcframework.annotations.CtService;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author linchong
 * @version 1.0
 * @Date: 2022-07-03 15:25
 * @Description: CustomDispatcherServlet$
 */
public class CtDispatcherServlet extends HttpServlet {

	private Properties properties = new Properties();

	//缓存扫描到的类的全限定类名
	private List<String> classNames = new ArrayList<>();

	//Ioc容器
	private Map<String,Object> ioc = new HashMap<>();

	//handleMapping,存储url和method之间的映射关系
	//private Map<String,Method> handlerMapping = new HashMap<>();
	private List<Handler> handlerMapping = new ArrayList<>();




	@Override
	public void init(ServletConfig config) throws ServletException {
		//1.加载配置文件，springmvc.properties
		String contextConfigLocation = config.getInitParameter("contextConfigLocation");
		doLoadConfig(contextConfigLocation);

		//2.扫描相关的类，扫描注解
		doScan(properties.getProperty("scanPackage"));

		//3.初始化bean对象（实现ioc对象，基于注解）
		//基于classNames缓存的类的全限定类名，以及反射技术，完成对象的创建和管理
		doInstance();

		//4.实现依赖注入
		doAutowired();

		/**
		 * 5.构造一个handlerMapping处理器映射器，将配置好的url和method建立映射关系
		 * 最关键环节
		 * 目的：将url和method建立关联
		 */

		initHandlerMapping();

		System.out.println("custom mvc 初始化完成...");
		//6.等待请求进入，处理请求


	}

	//加载配置文件
	private void doLoadConfig(String contextConfigLocation) {
		InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
		try {
			properties.load(resourceAsStream);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}


	//扫描类
	//scanPackage:com.lincong.mvcframework=>磁盘上的文件夹（file），com/lincong/mvcframework
	private void doScan(String scanPackage) {
		String scanPackagePath = Thread.currentThread().getContextClassLoader().getResource("").getPath()
				+scanPackage.replaceAll("\\.","/");
		File pack = new File(scanPackagePath);
		File[] files = pack.listFiles();
		for (File file : files) {
			if(file.isDirectory()){ //子package,
				//递归
				doScan(scanPackage+"."+file.getName());
			}else if(file.getName().endsWith(".class")){
				String className = scanPackage + "." + file.getName().replaceAll(".class", "");
				classNames.add(className);
			}
		}
	}

	//IOC容器
	//基于classNames缓存的全限定类名，以及反射技术，完成对象的创建和管理
	private void doInstance(){
		if(classNames.size() == 0) return;
		try{
			//com.lincong.demo.DemoController
			for (String className : classNames) {
				//反射创建对象
				Class<?> clazz = Class.forName(className);
				//区分注解，controller or service ?
				if(clazz.isAnnotationPresent(CtController.class)){
					//controller的id这里不处理，不取value，拿类的首字母小写作为id,保存到ioc中
					//DemoController
					String simpleName = clazz.getSimpleName();
					//demoController
					String lowerFirstSimpleName = lowerFirst(simpleName);
					//实例化类，放入ioc容器管理
					Constructor<?> constructor = clazz.getConstructor();
					Object instance = constructor.newInstance();
					ioc.put(lowerFirstSimpleName,instance);
				}else if(clazz.isAnnotationPresent(CtService.class)){
					CtService annotation = clazz.getAnnotation(CtService.class);
					//获取注解的value值
					String beanName = annotation.value();
					Constructor<?> constructor = clazz.getConstructor();
					//如果指定id,以指定的为准
					if(!"".equals(beanName.trim())){

						ioc.put(beanName,constructor.newInstance());
					}else{
						//如果没有指定，以类名首字母小写
						beanName = lowerFirst(clazz.getSimpleName());
						ioc.put(beanName,constructor.newInstance());
					}
					//Service层通常都有接口，面向接口开发，此时在以接口名为id,放入一份对象到ioc,便于后续的根据接口类型注入
					Class<?>[] interfaces = clazz.getInterfaces();
					for (Class<?> anInterface : interfaces) {
						//以接口的全限定类名作为id放入ioc
						ioc.put(anInterface.getName(),constructor.newInstance());
					}
				}else{
					continue;
				}
			}
		}catch (Exception e){
			e.printStackTrace();
		}

	}

	public String lowerFirst(String str) {
		char[] chars = str.toCharArray();
		if ('A' <= chars[0] && chars[0] <= 'Z') {
			chars[0] += 32;
		}
		return String.valueOf(chars);
	}


	//构造处理器映射器
	private void initHandlerMapping() {
		if(ioc.isEmpty()) return;
		for (Map.Entry<String, Object> entry : ioc.entrySet()) {
			//获取IOC中当前遍历对象的class类型
			Class<?> aClass = entry.getValue().getClass();


			if(!aClass.isAnnotationPresent(CtController.class))
				continue;

			String baseUrl = "";
			if(aClass.isAnnotationPresent(CtRequestMapping.class)){
				CtRequestMapping annotation = aClass.getAnnotation(CtRequestMapping.class);
				baseUrl = annotation.value(); //等同于 /demo
			}

			//获取方法
			Method[] methods = aClass.getMethods();
			for (Method method : methods) {
				//方法没有表示CtRequestMapping,不处理
				if(!method.isAnnotationPresent(CtRequestMapping.class))
					continue;

				CtRequestMapping annotation = method.getAnnotation(CtRequestMapping.class);
				String methodUrl = annotation.value(); // /query
				String url = baseUrl+methodUrl; //匹配出的url，对应 /demo/query
				//将method所有信息以及url封装为一个Handler
				Handler handler = new Handler(entry.getValue(),method, Pattern.compile(url));
				//计算参数的位置信息 //query(HttpServlet request,HttpServletResponse response,String name)

				Parameter[] parameters = method.getParameters();
				for (int i = 0; i < parameters.length; i++) {
					Parameter parameter = parameters[i];
					if(parameter.getType() == HttpServletRequest.class
							|| parameter.getType() == HttpServletResponse.class){
						//如果是request和response对象，参数名称写HttpServletRequest和HttpServletResponse
						handler.getParamIndexMapping().put(parameter.getType().getSimpleName(),i);
					}else{
						handler.getParamIndexMapping().put(parameter.getName(),i); //<name,2>
					}
				}


				//建立url和method之间的映射关系（缓存，map）
				//handlerMapping.put(url,method);
				handlerMapping.add(handler);
			}

		}
	}

	//实现依赖注入
	private void doAutowired() {
		if(ioc.isEmpty()) return;
		//有对象，再进行依赖注入处理
		//遍历ioc中所有对象，查看对象的字段，是否有@CtAutowired注解，如果有，则需要维护依赖注入关系
		for(Map.Entry<String,Object> entry:ioc.entrySet()){
			//获取bean对象中的字段信息
			Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();
			//遍历判断处理
			//@CtAutowired
			for (Field declaredField : declaredFields) {
				if(!declaredField.isAnnotationPresent(CtAutowired.class))
					continue;
				//有该注解，
				CtAutowired annotation = declaredField.getAnnotation(CtAutowired.class);
				String beanName = annotation.value();
				if("".equals(beanName.trim())){
					//没有配置具体的bean id,则需要根据当前字段的类型注入（接口注入）IDemoService
					beanName = declaredField.getType().getName();
				}
				//开启赋值
				declaredField.setAccessible(true);
				try {
					declaredField.set(entry.getValue(),ioc.get(beanName));
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				}
			}
		}
		
	}








	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		super.doPost(req, resp);
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		//处理请求，根据url,找到对应的method方法，进行调用
		//获取url
//		String requestURI = req.getRequestURI();
//		Method method = handlerMapping.get(requestURI);
		//反射调用，需要传入对象，需要传入参数，此处无法完成调用，只缓存了方法，没有把对象缓存起来，也没有参数，改造initHandlerMapping()
		//method.invoke()

		//根据url获取到能够处理当前请求的handler(从handlermapping中（list))
		Handler handler = getHandler(req);
		if(null == handler){
			resp.getWriter().write("404 not found");
			return;
		}
		//参数绑定（获取参数类型）
		//获取所有参数类型数组，这个数组的长度就是我们最后要传入的args数组的长度
		Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();
		//根据上述数组长度创建一个新的数组(参数数组，是要传入反射调用)
		Object[] paraValues = new Object[parameterTypes.length];

		//以下向参数数组中填充值，而且还要保证参数的顺序和方法中形参的顺序一致
		Map<String,String[]> parameterMap = req.getParameterMap();
		//遍历request中所有的参数（普通参数，除了request,response）
		for(Map.Entry<String,String[]> param:parameterMap.entrySet()){
			//name=1&name=2
			String value = StringUtils.join(param.getValue(), ","); //如同1,2
			//如果参数和方法中的参数匹配上了，填充数据
			if(!handler.getParamIndexMapping().containsKey(param.getKey())) continue;

			//包含该参数，找到其索引位置，对应的把参数放入paraValues
			Integer index = handler.getParamIndexMapping().get(param.getKey());//name在第2个位置
			paraValues[index] = value; //把前台传递来的参数放到对应的位置上
		}


		int requestIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName()); //0
		paraValues[requestIndex] = req;
		int responseIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName());
		paraValues[responseIndex] = responseIndex;

		//最终调用handler中的method属性
		try {
			handler.getMethod().invoke(handler.getController(),paraValues);
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}

	}

	private Handler getHandler(HttpServletRequest req) {
		if(handlerMapping.isEmpty()) return  null;
		String url = req.getRequestURI();

		for (Handler handler : handlerMapping) {
			Matcher matcher = handler.getPattern().matcher(url);
			if(!matcher.matches()) continue;
			return handler;
		}
		return null;
	}
}
